import os
from PyQt4.QtGui import *
from S3D.Util import s3dws
from S3D import XboxShell, QtHelper
from S3D.SoundsDeployer.UI.MainWindow import Ui_MainWindow
from S3D.Decorators import thread
from multiprocessing import Lock
from threading import Thread
from time import sleep

IGNORE_WORKSPACES = {
    "_registryRoot",
    "Perforce",
    "ToolsFolders",
}

SRC_SUBPATH = r"xbox360"
DST_SUBPATH = r"sounds\xbox360"

ERROR_LABEL   = "* failed to connect"
UPDATE_LABEL  = "updating..."

RUN_STATE_CHAR = "|/-\\"
RUN_STATE_SPEED_INV = 2
RUN_STATES_COUNT = len(RUN_STATE_CHAR)

def _makeItem(name):
    item = QTreeWidgetItem([name])
    item.name = name
    return item

def _removeAllChilds(item):
    while item.childCount() > 0:
        item.removeChild(item.child(0))

def _updateItemRunIcon(item, fmt):
    if not hasattr(item, "runState"):
        item.runState = 0
    else:
        item.runState = (item.runState + 1) % (RUN_STATE_SPEED_INV * RUN_STATES_COUNT)

    text = fmt.format(char=RUN_STATE_CHAR[item.runState // RUN_STATE_SPEED_INV], name=item.name)
    item.setText(0, text)

def _stopItemRunIcon(item):
    if not hasattr(item, "runState"):
        return

    del item.runState
    item.setText(0, item.name)

def _setItemMessage(item, message):
    _stopItemRunIcon(item)

    item.setText(0, "%s - %s" % (item.name, message))

class RefreshData:
    def __init__(self, item):
        self.lock = Lock()
        self.isFinished = False
        self.buildList  = None
        self.item = item

class DeployData:
    def __init__(self, item):
        self.lock = Lock()
        self.isFinished = False
        self.item = item
        self.returnCode = 0

class MainWindow(QMainWindow):
    def __init__(self, config):
        super(MainWindow, self).__init__()

        self.cfg = config

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.refreshData = []
        self.deployData  = []
        self.haveUnshownErrors = False

        self.initialization = True
        self.initWorkspaces()
        self.initXboxTree()
        self.initialization = False

        self.startTimer(100)

        self.show()

    def initWorkspaces(self):
        self.workspaces = [p for p in s3dws.getWorkspaceList() if p not in IGNORE_WORKSPACES]

        try:
            index = self.workspaces.index(self.cfg.workspace)
        except:
            index = 0

        self.ui.workspaceBox.addItems(self.workspaces)
        self.ui.workspaceBox.setCurrentIndex(index)
        self.cfg.workspace = self.workspaces[index]
        s3dws.setWorkspace(self.cfg.workspace)

    def initXboxTree(self):
        for name in self.cfg.xboxList:
            item = _makeItem(name)
            self.ui.buildsTree.addTopLevelItem(item)
            self.refreshItem(item)

    def onWorkspaceChanged(self, index):
        if index == -1 or self.initialization:
            return

        self.cfg.workspace = self.workspaces[index]

        s3dws.setWorkspace(self.cfg.workspace)
        self.onRefresh()

    def onAdd(self):
        name = QtHelper.getValue(self, self.tr("Enter Xbox360 name"), self.tr("Name:"))
        if name in self.cfg.xboxList:
            QMessageBox.warning(self, self.tr("Warning"), self.tr("Xbox360 with such name already exists"), QMessageBox.Ok)
            return

        self.cfg.xboxList.append(name)

        item = _makeItem(name)
        self.ui.buildsTree.addTopLevelItem(item)

        self.refreshItem(item)
        self.ui.buildsTree.update()

    def onRemove(self):
        item = self.ui.buildsTree.currentItem()
        if not item:
            return

        name = item.text(0)

        res = QMessageBox.warning(self, self.tr("Remove Xbox360"), self.tr("Are you sure you want to remove this Xbox360?"), QMessageBox.Yes | QMessageBox.No)
        if res != QMessageBox.Yes:
            return

        self.cfg.xboxList.remove(name)
        self.ui.buildsTree.removeItemWidget(item, 0)
        self.ui.buildsTree.update()

    def onRefresh(self):
        self.refreshData = []
        self.deployData = []

        count = self.ui.buildsTree.topLevelItemCount()
        for k in range(count):
            item = self.ui.buildsTree.topLevelItem(k)
            self.refreshItem(item)

    def onDeploy(self):
        item = self.ui.buildsTree.currentItem()
        if not item or not hasattr(item, "path"):
            return

        self.deployItem(item)

    def timerEvent(self, e):
        if self.initialization:
            return

        if self.refreshData:
            self.updateRefresh()

        if self.deployData:
            self.updateDeploy()

    def updateRefresh(self):
        finished = True

        for index in range(len(self.refreshData)):
            task = self.refreshData[index]
            if not task:
                continue

            with task.lock:
                if not task.isFinished:
                    finished = False
                    _updateItemRunIcon(task.updateItem, "{char} {name}")
                    continue

                self.refreshData[index] = None

                item      = task.item
                buildList = task.buildList

            _removeAllChilds(item)

            if buildList is None:
                item.addChild(_makeItem(ERROR_LABEL))
                continue

            for name, path in sorted(buildList):
                child = _makeItem(name)
                child.path = path
                child.deploy = None
                item.addChild(child)

            self.ui.buildsTree.update()

        if finished:
            self.refreshData = []

    def updateDeploy(self):
        finished = True

        for index in range(len(self.deployData)):
            task = self.deployData[index]
            if not task:
                continue

            with task.lock:
                if not task.isFinished:
                    finished = False
                    _updateItemRunIcon(task.item, "{name} - {char}")
                    continue

                self.deployData[index] = None

                item = task.item
                returnCode = task.returnCode

            _setItemMessage(item, "done." if returnCode == 0 else "failed.")
            if returnCode != 0:
                self.haveUnshownErrors = True

        if finished:
            self.deployData = []

            if self.haveUnshownErrors:
                self.haveUnshownErrors = False
                QMessageBox.warning(self, "Failed", "Deployment finished with errors.", QMessageBox.Ok)
            else:
                QMessageBox.information(self, "Done", "Deployment complete.", QMessageBox.Ok)

    def refreshItem(self, item):
        name = item.text(0)

        # create task
        data = RefreshData(item)

        # set temporary text
        _removeAllChilds(item)

        updateItem = _makeItem(UPDATE_LABEL)
        item.addChild(updateItem)
        item.setExpanded(True)

        data.updateItem = updateItem

        refreshTask(name, data, self.cfg)
        self.refreshData.append(data)

    def deployItem(self, item):
        if item.deploy is not None:
            return

        data = DeployData(item)
        deployTask(item.parent().name, data)
        self.deployData.append(data)

@thread
def refreshTask(xbox, data, config):
    try:
        def _search(path, exe, deep):
            # search for .xex
            res = XboxShell.listDir(os.path.join(path, exe), xbox)

            # test .xex
            for candidate in res or []:
                _, ext = os.path.splitext(candidate)
                if ext.lower() == '.xex':
                    return path

            # check subfolders
            if not deep:
                return None

            subPath = XboxShell.listDir(os.path.join(path, "*.*"), xbox)
            if not subPath:
                return None

            for name in subPath:
                res = _search(os.path.join(path, name), exe, deep - 1)
                if res is not None:
                    return res

            return None

        candidates = XboxShell.listDir("xE:\\*.*", xbox)
        if candidates is None:
            return

        res = []
        for name in candidates:
            path = _search("xE:\\%s" % name, "%s*.xex" % config.workspace, 1)
            if path:
                res.append((name, path))

        with data.lock:
            data.buildList = res
    finally:
        with data.lock:
            data.isFinished = True

@thread
def deployTask(xbox, data):
    try:
        src = s3dws.getRecordWs("sound_bin_dir")
        src = os.path.join(src, SRC_SUBPATH, "*.*")
        dst = os.path.join(data.item.path, DST_SUBPATH)

        proc = XboxShell.copy(src, dst, xbox)
        err = proc.stderr.readlines()
        proc.wait()

        with data.lock:
            data.returnCode = proc.returncode or not len(err)
    finally:
        with data.lock:
            data.isFinished = True
